﻿// <copyright file="Settings.cs" company="Hallvard Korsgaard">
//  Microsoft Public License (Ms-PL). See license details here: http://enoceannet.codeplex.com/license
// </copyright>
// <author>Hallvard Korsgaard</author>

namespace StcEthernet.Management
{
  using System;
  using System.Net;
  using System.Net.Sockets;
  using System.Text;
  using System.Threading;

  /// <summary>
  /// Finds STC-Ethernet transievers on the LAN and reads theirs settings. Modified settings can be sent back to update the transievers.
  /// </summary>
  [Serializable]
  public class Settings
  {
    /// <summary>
    /// Socket to listen for replies from STC-Ethernet transievers.
    /// </summary>
    private Socket serverSocket;
    
    /// <summary>
    /// Initializes a new instance of the Settings class.
    /// The class creates a UDP socket that listens on port 5001. That port must be open
    /// in the firewall in order for this class to work. The Find() and Update() methods sends
    /// a UDP broadcast to the remote port 1460. That operation must also be allowed in the firewall.
    /// </summary>
    public Settings()
    {
      SocketAsyncEventArgs readArgs;
      byte[] receiveBuffer;
      bool pending;

      receiveBuffer = new byte[50];

      this.serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
      this.serverSocket.EnableBroadcast = true;
      this.serverSocket.Bind(new IPEndPoint(IPAddress.Any, 5001));

      readArgs = new SocketAsyncEventArgs();
      readArgs.RemoteEndPoint = new IPEndPoint(IPAddress.Broadcast, 1460);
      readArgs.AcceptSocket = this.serverSocket;
      readArgs.SetBuffer(receiveBuffer, 0, receiveBuffer.Length);
      readArgs.Completed += new EventHandler<SocketAsyncEventArgs>(this.ReadCompleted);

      pending = this.serverSocket.ReceiveFromAsync(readArgs);

      if (!pending)
      {
        this.ReadCompleted(this, readArgs);
      }
    }

    /// <summary>
    /// The event fires when a TransieverInfo message is received from the transiever.
    /// </summary>
    public event EventHandler<TransieverInfoReceivedEventArgs> TransieverInfoReceived = delegate { };

    /// <summary>
    /// The event fires if a message that could not be interpreted arrives.
    /// </summary>
    public event EventHandler<UnknownDataReceivedEventArgs> UnknownTelegramReceived = delegate { };

    /// <summary>
    /// Broadcasts a "FIND" message in the network.
    /// </summary>
    public void Find()
    {
      byte[] find;

      find = Encoding.ASCII.GetBytes("FIND");
      this.serverSocket.SendTo(find, new IPEndPoint(IPAddress.Broadcast, 1460));
    }

    /// <summary>
    /// Updates settings on the transiever.
    /// </summary>
    /// <param name="ti">A TransieverInfo object containing the new settings.
    /// The TransieverInfo object must be obtained using the Find method and then modified before it is passed to this method.</param>
    public void Update(TransieverInfo ti)
    {
      this.serverSocket.SendTo(ti.GetBytes(), new IPEndPoint(IPAddress.Broadcast, 1460));
    }

    /// <summary>
    /// Event handler for the SocketAsyncEventArgs.Completed event.
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">A SocketAsyncEventArgs that contains the event data.</param>
    private void ReadCompleted(object sender, SocketAsyncEventArgs e)
    {
      bool pending;
      TransieverInfo info;
      byte[] invalidData;

      if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
      {
        try
        {
          if (e.Offset + e.BytesTransferred < e.Buffer.Length)
          {
            e.SetBuffer(e.Offset + e.BytesTransferred, e.Buffer.Length - e.Offset - e.BytesTransferred);
          }
          else
          {
            info = new TransieverInfo(e.Buffer);
            ThreadPool.QueueUserWorkItem(state => this.TransieverInfoReceived(this, new TransieverInfoReceivedEventArgs(info)), null);
            e.SetBuffer(0, e.Buffer.Length);
          }

          pending = e.AcceptSocket.ReceiveFromAsync(e);
          if (!pending)
          {
            this.ReadCompleted(this, e);
          }
        }
        catch (Exception ex)
        {
          invalidData = new byte[e.Buffer.Length];
          Array.Copy(e.Buffer, invalidData, invalidData.Length);

          ThreadPool.QueueUserWorkItem(state => this.UnknownTelegramReceived(this, new UnknownDataReceivedEventArgs(ex, invalidData)), null);
          e.SetBuffer(0, e.Buffer.Length);
          pending = e.AcceptSocket.ReceiveFromAsync(e);
          if (!pending)
          {
            this.ReadCompleted(this, e);
          }
        }
      }
      else
      {
        // TODO: Create an error event.
      }
    }
  }
}

